package com.example.common.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.example.common.util.util_entity.Bean2DMLEnum;
import com.example.common.util.util_entity.Info;
import com.github.houbb.data.factory.core.util.DataUtil;
import io.swagger.annotations.ApiModelProperty;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import static cn.hutool.core.util.StrUtil.isBlank;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

/**
 * 反射工具类
 *
 * @author zjarlin
 * @since 2022/06/29
 */
@SuppressWarnings("unused")

public interface RefUtil {

    static String mock(Class<?> clazz) {
        Object build = DataUtil.build(clazz);
        return JSON.toJSONString(build);
    }

    /**
     * 打印diff bean文档
     *
     * @param beanA 豆doca
     * @param beanB 豆docb
     * @author zjarlin
     * @since 2022/06/13
     */

    static <A, B> void printDiffBean(Class<A> beanA, Class<B> beanB) {
        final List<BeanDoc> beanDoc = getBeanDoc(beanA);
        final List<BeanDoc> beanDoc1 = getBeanDoc(beanB);
        System.out.println("括号里形参A的属性个数X:" + beanDoc.size());
        System.out.println("括号里形参B的属性个数Y:" + beanDoc1.size());
        System.out.println(JSON.toJSON(diffBean(beanA, beanB)));
    }

    /**
     * 得到JavaBeanDocList(字段名,字段类型,字段注释)
     *
     * @param clazz clazz
     * @return {@link List }<{@link BeanDoc }>
     * @author zjarlin
     * @since 2022/06/13
     */
    static <T> List<BeanDoc> getBeanDoc(Class<T> clazz) {
        final Field[] fields = ReflectUtil.getFieldsDirectly(clazz, false);
        int[] a = {0};
        return Arrays.stream(fields).filter(Objects::nonNull).map(field -> {
            field.setAccessible(true);
            final String fieldName = field.getName();
            final String simpleTypeName = field.getType().getSimpleName();
            final String comment = ofNullable(field.getAnnotation(ApiModelProperty.class)).map(ApiModelProperty::value)
                .orElse("没有ApiModelProperty修饰" + a[0]++);
            return new BeanDoc(fieldName, simpleTypeName, comment);
        }).collect(Collectors.toList());
    }

    /**
     * diff bean文档
     *
     * @param beanA beana
     * @param beanB beanb
     * @return {@link List }<{@link DiffDoc }>
     * @author zjarlin
     * @since 2022/06/13
     */
    static <A, B> List<DiffDoc> diffBean(Class<A> beanA, Class<B> beanB) {

        final List<BeanDoc> beanDoc = getBeanDoc(beanA);

        final List<BeanDoc> beanDoc1 = getBeanDoc(beanB);
//
        if (beanDoc.size() < beanDoc1.size()) {
            System.out.println("由于beanA的属性少于beanB，所以自动翻转形参,打印的json里的A是括号里形参字段个数多的那个");
        }
        final List<BeanDoc> beanDocA = beanDoc.size() < beanDoc1.size() ? beanDoc1 : beanDoc;
        final List<BeanDoc> beanDocB = beanDoc.size() < beanDoc1.size() ? beanDoc : beanDoc1;
//
        final Map<String, BeanDoc> relationFromB = beanDocB.stream().collect(Collectors.toMap(BeanDoc::getFieldName, Function.identity()));
        final Map<String, BeanDoc> relationCommentFromB = beanDocB.stream().collect(Collectors.toMap(BeanDoc::getComment, Function.identity()));
        return beanDocA.stream().map(a -> {
            final String fieldNameA = a.getFieldName();
            final String simpleTypeNameA = a.getSimpleTypeName();
            final String commentA = ofNullable(a.getComment()).orElse("");

            //名字精确找
            final String fieldNameB = ofNullable(relationFromB.get(fieldNameA)).map(BeanDoc::getFieldName).orElse(
                beanDocB.stream().filter(bs -> {
                    final String fieldName = bs.getFieldName();
//                      名字正反向模糊找 :考虑反向包含的情况(可能子串包含但语义不同,会有bug)   比如A里  abcQWabcEasdas   B里bc||ab||QW 也能like出来
                    return containsIgnoreOrder(fieldName, fieldNameA);
                }).map(BeanDoc::getFieldName).findAny().orElse(
                    // 注释精确找
                    ofNullable(relationCommentFromB.get(commentA)).map(BeanDoc::getFieldName).orElse(
                        beanDocB.stream().filter(bs -> {
//                                        注释模糊找
                            final String bsComment = ofNullable(bs.getComment()).orElse("");
                            return containsIgnoreOrder(bsComment, commentA);
                        }).map(BeanDoc::getFieldName).findAny().orElse("")
                    )));
            final String simpleTypeNameB = ofNullable(relationFromB.get(fieldNameA)).map(BeanDoc::getSimpleTypeName).orElse(
                beanDocB.stream().filter(bs -> {
                    final String simpleTypeName = bs.getSimpleTypeName();
                    return containsIgnoreOrder(simpleTypeName, simpleTypeNameA);
                }).map(BeanDoc::getSimpleTypeName).findAny().orElse("")
            );

            final String commentB = ofNullable(relationFromB.get(fieldNameA)).filter(e -> Objects.nonNull(e.getComment())).map(BeanDoc::getComment).orElse(
                beanDocB.stream().filter(bs -> {
                    final String comment = ofNullable(bs.getComment()).orElse("");
                    return containsIgnoreOrder(comment, commentA);
                }).map(BeanDoc::getComment).findAny().orElse(
                    ofNullable(relationCommentFromB.get(commentA)).map(BeanDoc::getComment).orElse(
                        beanDocB.stream().filter(bs -> {
//                                        注释模糊找
                            final String bsComment = ofNullable(bs.getComment()).orElse("");
                            return containsIgnoreOrder(bsComment, commentA);
                        }).map(BeanDoc::getComment).findAny().orElse("")

                    )
                )
            );
            return new DiffDoc(fieldNameA, simpleTypeNameA, commentA, fieldNameB, simpleTypeNameB, commentB);
        }).collect(Collectors.toList());
    }

    /**
     * 包含忽略秩序
     *
     * @param seq       seq
     * @param searchSeq 搜索seq
     * @return 返回状态true/false
     * @author zjarlin
     * @since 2022/06/18
     */
    static boolean containsIgnoreOrder(final CharSequence seq, final CharSequence searchSeq) {
        return StringUtils.contains(seq, searchSeq) || StringUtils.contains(searchSeq, seq);
    }

    static void main(String[] args) {
        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        final SFunction<Integer, String> lineHandler = String::valueOf;
        final Cloneable generic4SerializedFunction1 = getGeneric4SerializedFunction(lineHandler);
        System.out.println(generic4SerializedFunction1);
    }

    /**
     * 利用SerializedLambda获取Function的入参出参泛型
     * 有bug
     *
     * @param fun 序列化的function
     * @return 返回信息
     * @author zjarlin
     * @since 2022/07/27
     */
    @SneakyThrows
    static <T, R, F extends Function<T, R> & Serializable> Cloneable getGeneric4SerializedFunction(F fun) {
        final Class<? extends Function> aClass = fun.getClass();
        final ClassLoader classLoader = aClass.getClassLoader();
        Method writeReplace = aClass.getDeclaredMethod("writeReplace");
        writeReplace.setAccessible(true);
        SerializedLambda serializedLambda = (SerializedLambda) writeReplace.invoke(fun);
        final String instantiatedMethodType = serializedLambda.getInstantiatedMethodType();
        final MethodType methodType = MethodType.fromMethodDescriptorString(instantiatedMethodType, classLoader);
        final Class<?> parameterType = methodType.parameterType(0);
        final Class<?> returnType = methodType.returnType();
        return new HashMap<Class<?>, Class<?>>(1) {{
            put(parameterType, returnType);
        }};
    }

    /**
     * 反射copy集合 List<A> -> List<B>  字段名,类型相同就copy
     *
     * @param resources
     * @return {@link List }<{@link T }>
     * @author addzero
     * @since 2022/09/21
     */
    @SneakyThrows
    static <T> List<T> copyCollection(List<T> resources) {
        return copyCollection(resources, (Class<T>) resources.get(0).getClass());
    }

    //public static <T> void dasDash(Collection<T> src,Collection<T> tar) {
    //
    //    List<T> studDao = new ArrayList<T>();
    //    List<student> list = studentService.list();
    //    list.forEach(stu ->{
    //        TuserVO = new T();
    //        BeanUtils.copyProperties(stu, userVO);
    //        studDao.add(userVO);
    //    });
    //}

    /**
     * 反射copy集合 List<A> -> List<B>  字段名,类型相同就copy
     *
     * @param resources
     * @param clz       入参
     * @return {@link List }<{@link T }>
     * @author addzero
     * @since 2022/09/21
     */
    @SneakyThrows
    static <T> List<T> copyCollection(List<?> resources, Class<T> clz) {
        if (CollUtil.isEmpty(resources)) {
            return Collections.emptyList();
        }
        List<T> result = new ArrayList<>();
        T single;
        // 获取源集合泛型的class
        Class<?> sourceClass = resources.get(0).getClass();
        List<Field> sourceFields = new ArrayList<>();
        // 递归获取自己和父类的字段
        sourceFields = getSuperField(sourceClass, sourceFields);
        // 获取目标类型的字段
        List<Field> targetFields = new ArrayList<>();
        targetFields = getSuperField(clz, targetFields);
        // 转成 key 是字段名， value 是字段类型的 map，方便校验
        Map<String, ? extends Class<?>> fieldNameTypeMap = targetFields.stream().collect(Collectors.toMap(Field::getName, Field::getType));

        PropertyDescriptor propertyDescriptor;
        for (Object resource : resources) {
            // 实例化目标类型
            single = clz.newInstance();
            for (Field declaredField : sourceFields) {
                declaredField.setAccessible(true);
                String fieldName = declaredField.getName();
                // 字段不被 finla 修饰，并且目标类包含此字段，并且字段类型一致才能继续走逻辑
                if (Modifier.isFinal(declaredField.getModifiers())
                    || !fieldNameTypeMap.containsKey(fieldName)
                    || !fieldNameTypeMap.get(fieldName).equals(declaredField.getType())) {
                    continue;
                }
                // 获取字段值
                Object value = declaredField.get(resource);
                // 获取字段的 set 方法，并设置值
                propertyDescriptor = new PropertyDescriptor(fieldName, clz);
                propertyDescriptor.getWriteMethod().invoke(single, value);
            }
            result.add(single);
        }

        return result;
    }

    /**
     * 递归获取自己和父类的全部字段,包括父类的父类
     *
     * @param clz
     * @param result
     * @return
     */
    static List<Field> getSuperField(Class<?> clz, List<Field> result) {
        Class<?> superclass = clz.getSuperclass();
        result.addAll(Arrays.asList(clz.getDeclaredFields()));
        if (ObjectUtils.isEmpty(superclass)) {
            return result;
        }
        return getSuperField(superclass, result);

    }

    /**
     * 得到实际类型
     *
     * @param t t 入参
     * @return 返回实际类型
     * @author zjarlin
     * @since 2022/07/26
     */
    static <T> Type getActualType(T t) {
        final Type genericSuperclass = t.getClass().getGenericSuperclass();
        return ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    }

    /**
     * 判断下层接口返回的结果是否是new出来的对象
     *
     * @param object 下层接口返回的obj
     * @return 返回状态true/false
     * @author zjarlin
     * @since 2022/06/11
     */
    static boolean isNew(Object object) {
        Class<?> aClass = object.getClass();
        Field[] fields = aClass.getDeclaredFields();
        return Arrays.stream(fields)
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .map(field -> {
                field.setAccessible(true);
                try {
                    return field.get(object);
                } catch (IllegalAccessException e) {
                    return null;
                }
            })
            .allMatch(value -> value == null ||
                               (value instanceof String && StringUtils.isBlank((String) value)) ||
                               (value instanceof Collection && CollUtil.isEmpty((Collection<?>) value)));
    }

    /**
     * 填充字段
     *
     * @param t      t
     * @param setFun set方法
     * @param getFun get方法
     * @author zjarlin
     * @since 2022/06/29
     */
    static <T, S, F> void fillField(T t, BiConsumer<T, F> setFun, Function<S, F> getFun) {
        fillField(t, new Info<>(setFun, getFun));
    }

    /**
     * 填充字段
     *
     * @param s     s
     * @param infos 信息
     * @param t     t
     * @author zjarlin
     * @since 2022/06/29
     */
    @SafeVarargs
    static <T, S, F> void fillField(T t, S s, Info<T, S, F>... infos) {
        Arrays.stream(infos).filter(info -> Objects.nonNull(info.getFun.apply(s))).forEach(info -> {
            final F apply = info.getFun.apply(s);
            if (isNotBlank(apply instanceof String ? ((String) apply) : null)) {
                info.setFun.accept(t, apply);
                return;
            }
            info.setFun.accept(t, (F) (apply instanceof Integer ? ((Integer) apply) : null));
        });
    }

    /**
     * 填充字段
     *
     * @param s      s
     * @param t      t
     * @param setFun set方法
     * @param getFun get方法
     * @author zjarlin
     * @since 2022/06/29
     */
    static <T, S, F> void fillField(T t, S s, BiConsumer<T, F> setFun, Function<S, F> getFun) {
        fillField(t, s, new Info<>(setFun, getFun));
    }

    @SneakyThrows
    static void printAddDML() {
        StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2];
        String className = stackTraceElement.getClassName();
        Class<?> aClass = Class.forName(className);
        printAddDML(aClass);
    }

    static void printAddDML(final Class<?> aClass) {
        List<String> dmls = getAddDMLs(aClass);
        //String sqlDML = (String) comment;
        dmls.forEach(System.out::println);

        //System.out.println(sqlDML);
    }

    @NotNull
    public static List<String> getAddDMLs(Class<?> aClass) {
        String tabName =
            Optional.ofNullable(aClass.getAnnotation(TableName.class))
                .map(TableName::value)
                .orElse("");
        Field[] declaredFields = ReflectUtil.getFields(aClass);

        List<String> dmls = Arrays.stream(declaredFields)
            .filter(field -> !field.isAnnotationPresent(TableField.class) || field.getAnnotation(TableField.class).exist())
            //过滤静态字段
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .map(field -> {
                String name = field.getName();

                String colComment = ofNullable(field.getAnnotation(ApiModelProperty.class))
                    .map(ApiModelProperty::value)
                    .orElse("");

                String col = StrUtil.toUnderlineCase(name);
                Class<?> type = field.getType();
                String colType = Bean2DMLEnum.get(type, Bean2DMLEnum::getSqlType);
                String colLength = Bean2DMLEnum.get(type, Bean2DMLEnum::getLength);
                String dml = StrUtil.concat(false, "alter table `"
                    , tabName
                    , "` add "
                    , "`", col, "` ", colType, colLength, " null ", "comment ", "'", colComment, "'");
                return dml + ";";
            }).collect(Collectors.toList());
        return dmls;
    }

    static void printChangeDML(final Class<?> aClass
        , String oldColName
        , String newColName
        , Bean2DMLEnum newColType, String newColComment) {
        String tabName =
            Optional.ofNullable(aClass.getAnnotation(TableName.class))
                .map(TableName::value)
                .orElse("");
        Field[] declaredFields = aClass.getDeclaredFields();

        String sqlDML = Arrays.stream(declaredFields)
            .filter(e -> StrUtil.equals(e.getName(), oldColName))
            .map(field -> {

                String name = field.getName();

                String colComment =
                    isBlank(newColComment) ?
                        Optional.ofNullable(field.getAnnotation(ApiModelProperty.class))
                            .map(ApiModelProperty::value)
                            .orElse("") : newColComment;

                String col = StrUtil.toUnderlineCase(name);

                String newCol = isBlank(newColName) ? col : StrUtil.toUnderlineCase(newColName);
                Class<?> type = field.getType();

                Class<?> newType =
                    Optional.ofNullable(newColType)
                        .map(e -> e.javaType)
                        .orElse(null);
                String colType = newColType == null
                    ? Bean2DMLEnum.get(type, Bean2DMLEnum::getSqlType)
                    : Bean2DMLEnum.get(newType, Bean2DMLEnum::getSqlType);

                String colLength =
                    newColType == null
                        ? Bean2DMLEnum.get(type, Bean2DMLEnum::getLength)
                        : Bean2DMLEnum.get(newType, Bean2DMLEnum::getLength);

                String dml = StrUtil.concat(false, "alter table `"
                    , tabName
                    , "` change "
                    , "`", col, "` "
                    , "`", newCol, "` "
                    , colType, colLength, " null ", "comment ", "'", colComment, "'");
                return dml;
            }).collect(Collectors.joining(";" + System.lineSeparator()));
        System.out.println(sqlDML);
    }

    @SneakyThrows
    static <AS extends Annotation, A extends Annotation> Map<String, List<A>> findAnnoMap(Class<?> aClass, Class<A> annotationClass, Class<AS> annotationClass1
        , final Function<AS, A[]> fun) {
        Field[] declaredFields = aClass.getDeclaredFields();
        return Arrays.stream(declaredFields)
            .peek(e -> e.setAccessible(true))
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .filter(field -> !field.getType().isAssignableFrom(Collection.class))
            .filter(e -> {
                boolean annotationPresent = e.isAnnotationPresent(annotationClass);
                boolean annotationPresent1 = e.isAnnotationPresent(annotationClass1);
                return
                    (annotationPresent || annotationPresent1);
            })
            .collect(Collectors.toMap(Field::getName, e -> {

                A declaredAnnotation1 = e.getDeclaredAnnotation(annotationClass);
                Optional<A> declaredAnnotation = ofNullable(declaredAnnotation1);
                Optional<A[]> dictCode2Names = ofNullable(e.getDeclaredAnnotation(annotationClass1)).map(fun);
                //return declaredAnnotation.map(Arrays::asList).orElseGet(() -> dictCode2Names.stream().flatMap(Arrays::stream).collect(Collectors.toList()));
                return declaredAnnotation.map(Arrays::asList).orElseGet(() -> dictCode2Names.map(Arrays::stream).orElseGet(Stream::empty).collect(Collectors.toList()));
            }));
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class BeanDoc {
        /**
         * 字段名称
         */
        private String fieldName;

        /**
         * 字段类型
         */
        private String simpleTypeName;

        /**
         * 字段描述
         */
        private String comment;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class DiffDoc {
        /**
         * 字段名称
         */
        private String fieldNameA;

        /**
         * 字段类型
         */
        private String simpleTypeNameA;

        /**
         * 字段描述
         */
        private String commentA;

        /**
         * 字段名称
         */
        private String fieldNameB;

        /**
         * 字段类型
         */
        private String simpleTypeNameB;

        /**
         * 字段描述
         */
        private String commentB;
    }

}
